
#include <vector>
#include "../iface/debug.h"
#include <apr_strings.h>
#include "../utils/StringUtils.h"
#include "HTTPServer.h"

#include "acquire_config.h"

apr_table_t *HTTPServer::GetTable(int i, apr_pool_t *mp, apr_size_t clength, const char *separator, const char *eol)
{
   int isfirst = 1;
   apr_table_t *htable = apr_table_make(mp, MAX_HEADERS);
   apr_size_t consumedbytes = 0;
   apr_size_t length_last_time = 0;
   char *linebuffer = (char *)malloc(sizeof(char)*MAX_BLOCK_SIZE);
   while (!(pipearray[i].recv_to_handler->IsClosed()))
   {
      apr_size_t length_this_time = pipearray[i].recv_to_handler->RealLength();
      if ((length_last_time != length_this_time) ||
          ((length_this_time + consumedbytes) >= clength))
      {
         int realbytes = pipearray[i].recv_to_handler->Read(linebuffer, MAX_BLOCK_SIZE - 1);
         linebuffer[realbytes] = '\0';
         char *k = strstr(linebuffer, eol);
         if ((!k) && ((length_this_time + consumedbytes) >= clength))
            k = linebuffer + strlen(linebuffer);
         if (k)
         {
            if (k == linebuffer)
            {
               pipearray[i].recv_to_handler->Delete(strlen(eol));
               free(linebuffer);
               return htable;
            } else
            {
               int vallen = (k - linebuffer) + strlen(eol);
               char *newheader = (char *)apr_palloc(mp, sizeof(char)*(vallen+1));
               memcpy(newheader, linebuffer, k - linebuffer);
               newheader[k - linebuffer] = '\0';
               char *tremainder = NULL;
               char *bits = apr_strtok(newheader, separator, &tremainder);
               // isfirst is a fudge to remove extra characters in later headers and
               // make the first header line in keeping (somewhat) with the rest
               if (tremainder[0] == ' ') tremainder++;
               apr_table_set(htable, bits, tremainder);
               pipearray[i].recv_to_handler->Delete(vallen);
               consumedbytes += vallen;
               isfirst = 0;
            }
         } else
         {
            if (length_this_time > (MAX_BLOCK_SIZE - 1))
            {
               pipearray[i].handler_to_send->Close();
               errprint("data too big");
               free(linebuffer);
               return NULL;
            }
         }
         length_last_time = length_this_time;
      } else pipearray[i].waiting->wait();
   }
   free(linebuffer);
   return htable;
}

apr_table_t *HTTPServer::GetHeaders(int i, apr_pool_t *mp)
{
   return GetTable(i, mp, (apr_size_t)-1, ": ", CRLF_STR);
}

apr_table_t *HTTPServer::GetPOSTData(int i, apr_pool_t *mp, apr_size_t clength)
{
   return GetTable(i, mp, clength, "=", "&");
}

apr_size_t HTTPServer::GetContentLength(apr_table_t *htable)
{
   const char *clength_str = apr_table_get(htable, "Content-Length");
   if (clength_str)
   {
      apr_size_t clength_true = 0;
      sscanf(clength_str, "%" APR_SIZE_T_FMT, &clength_true);
      return clength_true;
   }
   return (apr_size_t)-1;
}

int HTTPServer::ServeFilesFromWhiteList(apr_table_t *htable, int i, apr_pool_t *mp, const char **fileswhitelist)
{
   apr_status_t status;
   char *dstr = apr_pstrdup(mp, apr_table_get(htable, "GET"));
   char *httpver = NULL;
   char *firsttok = apr_strtok(dstr, " ", &httpver);
   int count = 0;
   int passes = 0;
   // see if it matches any in the white list
   while (fileswhitelist[count] != NULL)
      passes |= (strcmp(fileswhitelist[count++], firsttok) == 0);
   if (passes == 0)
   {
      if (strcmp(StaticStringGetClientAddress(i), "127.0.0.1") != 0)
         errprint("%s tried to connect but '%s' not on whitelist!", StaticStringGetClientAddress(i), firsttok);
      return 0;
   }
   apr_file_t *newFP;
   char *filetoget = (char *)apr_palloc(mp, sizeof(char)*(strlen(ACQUIRE_DYNAMIC_DIR)+1+strlen(firsttok)+1));
   filetoget[0] = '\0';
   strcat(filetoget, ACQUIRE_DYNAMIC_DIR);
   strcat(filetoget, "/");
   strcat(filetoget, firsttok);
   status = apr_file_open(&newFP, StringUtils::expandMyHomeDirectoryTilde(filetoget, mp), APR_FOPEN_READ|APR_FOPEN_BINARY, APR_FPROT_OS_DEFAULT, mp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return 0; }
   const char *firstheader = "HTTP/1.0 200 OK" CRLF_STR;
   pipearray[i].handler_to_send->Write(firstheader, strlen(firstheader));
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   status = apr_file_close(newFP);
   CheckAPRError(status);
   pipearray[i].handler_to_send->Writef("Content-Length: %" APR_OFF_T_FMT, finfo.size);
   pipearray[i].handler_to_send->Write(CRLF_STR CRLF_STR, strlen(CRLF_STR)*2);
   // silly numbers to say - just use all of it!
   pipearray[i].handler_to_send->WriteFile(filetoget, 0, (apr_off_t)finfo.size, 0);
   pipearray[i].handler_to_send->WriteEOS();
   return 1;
}

void HTTPServer::ReplyWithSentence(int i, apr_pool_t *mp, const char *body)
{
   static const char *opening = "<html><h2>" CRLF_STR;
   static const char *closing = "</h2></html>" CRLF_STR;
   static const char *firstheader = "HTTP/1.0 200 OK" CRLF_STR;
   pipearray[i].handler_to_send->Write(firstheader, strlen(firstheader));
   apr_size_t clength = strlen(opening)+strlen(closing)+strlen(body);
   pipearray[i].handler_to_send->Writef("Content-Length: %" APR_SIZE_T_FMT CRLF_STR CRLF_STR, clength);
   pipearray[i].handler_to_send->Write(opening, strlen(opening));
   pipearray[i].handler_to_send->Write(body, strlen(body));
   pipearray[i].handler_to_send->Write(closing, strlen(closing));
   pipearray[i].handler_to_send->WriteEOS();
}

void HTTPServer::RedirectAfterPOST(int i, apr_pool_t *mp, const char *relocation)
{
   static const char *firstheader = "HTTP/1.0 302 Found" CRLF_STR;
   static const char *locheader = "Location: %s" CRLF_STR CRLF_STR;
   pipearray[i].handler_to_send->Write(firstheader, strlen(firstheader));
   pipearray[i].handler_to_send->Writef(locheader, relocation);
   pipearray[i].handler_to_send->WriteEOS();
}

void HTTPServer::IncrementUnlock(int i)
{
   apr_atomic_inc32(&(pipearray[i].stage));
   pipearray[i].waiting->unlock();
}

void HTTPServer::Error404(int i)
{
   const char *firstheader = "HTTP/1.0 404 Not Found" CRLF_STR;
   pipearray[i].handler_to_send->Write(firstheader, strlen(firstheader));
   pipearray[i].handler_to_send->WriteEOS();
   IncrementUnlock(i);
}

void HTTPServer::GETSession(int i, apr_pool_t *mp)
{
   static const char *fileswhitelist[] = { "/files/blue.html", NULL };
   pipearray[i].waiting->lock();
   apr_atomic_inc32(&(pipearray[i].stage));
   apr_table_t *headertable = GetHeaders(i, mp);
   apr_atomic_inc32(&(pipearray[i].stage));
   if (headertable == NULL) { Error404(i); errprint("Header table missing (out of memory?)."); return; }
   if (apr_is_empty_table(headertable)) { Error404(i); errprint("Header table empty (unknown error)."); return; }
   if (ServeFilesFromWhiteList(headertable, i, mp, fileswhitelist)) { IncrementUnlock(i); return; }
   Error404(i);
}

void HTTPServer::POSTSession(int i, apr_pool_t *mp)
{
   if (strcmp(StaticStringGetClientAddress(i), "127.0.0.1") != 0)
   {
      errprint("erroneous post by %s", StaticStringGetClientAddress(i));
      pipearray[i].handler_to_send->WriteEOS();
      apr_atomic_set32(&(pipearray[i].stage), SERVERSTAGE_SENDING);
      return;
   }
   pipearray[i].waiting->lock();
   apr_atomic_inc32(&(pipearray[i].stage));
   apr_table_t *headertable = GetHeaders(i, mp);
   apr_size_t contentlength = GetContentLength(headertable);
   apr_table_t *posttable = GetPOSTData(i, mp, contentlength);
   apr_atomic_inc32(&(pipearray[i].stage));
   RedirectAfterPOST(i, mp, "/hosted_files/blue.html");
   apr_atomic_inc32(&(pipearray[i].stage));
   pipearray[i].waiting->unlock();
}

void *HTTPServer::ClientHandler(apr_thread_t *tthread, void *args)
{
   apr_status_t status;
   HTTPServer *kthis = *((HTTPServer **)(((void **)(args))[0]));
   int i = *((int *)(((void **)(args))[1]));
   apr_pool_t *upperpool, *mp;
   upperpool = apr_thread_pool_get(tthread);
   status = apr_pool_create(&mp, upperpool);
   CheckAPRError(status);

   while (!(kthis->sessionstop))
   {
      kthis->pipearray[i].sessionstart->wait();
      if (kthis->sessionstop) break;
      apr_pool_t *sp;
      apr_pool_create(&sp, mp);
      while ((kthis->pipearray[i].recv_to_handler->RealLength() < 4) &&
             (!(kthis->pipearray[i].recv_to_handler->IsClosed())))
         apr_thread_yield();
      char fst[5];
      kthis->pipearray[i].recv_to_handler->Read(fst, 4); fst[4] = '\0';
      if (!(kthis->pipearray[i].recv_to_handler->IsClosed()))
      {
         if (strcmp(fst, "GET ") == 0)
         {
            kthis->GETSession(i, mp);
         } else if (strcmp(fst, "POST") == 0)
         {
            kthis->POSTSession(i, mp);
         } else
         {
            apr_atomic_set32(&(kthis->pipearray[i].stage), SERVERSTAGE_SENDING);
            kthis->pipearray[i].handler_to_send->Close();
         }
      } else
      {
         apr_atomic_set32(&(kthis->pipearray[i].stage), SERVERSTAGE_SENDING);
         kthis->pipearray[i].handler_to_send->Close();
      }
      apr_pool_clear(sp);
      apr_pool_destroy(sp);
   }
   kthis->pipearray[i].sessionstart->unlock();
   apr_pool_clear(mp);
   apr_pool_destroy(mp);
   ERR_remove_state(0);
   apr_thread_exit(tthread, APR_SUCCESS);
   return NULL;
}
